/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package parser;

import com.ericsson.nfc.signing.record.URIRecord;
import java.io.ByteArrayInputStream;
import javax.microedition.contactless.ndef.NDEFRecordType;

/**
 *
 * @author emarkki
 */
public class NDEFParser {

    private static final int MB = 0x80;
    private static final int ME = 0x40;
    private static final int CF = 0x20;
    private static final int SR = 0x10;
    private static final int IL = 0x08;
    private static final int TNF_MASK = 0x07;


    public String parse(byte[] message) {
        StringBuffer2 sb = new StringBuffer2();

        int _offset = 0;

        parseMessage(_offset, sb, message, 0);

        return sb.toString();
    }

    private int parseMessage(int _offset, StringBuffer2 sb, byte[] data, int offset) {

        ByteArrayInputStream in = new ByteArrayInputStream(data, offset, data.length);

        int hdr;

        do {

            hdr = in.read();
            _outputHeader(_offset++, hdr);
            
            int typeLength = in.read();
            System.out.println(_offset++ + "\t0x"+Integer.toHexString(typeLength)+"\tLength of the Record Type ("+typeLength+" byte)");

            int payloadLength;

            if((hdr & SR) != 0) {
                payloadLength = in.read();

                _outputPayloadLength(_offset, payloadLength, new byte[]{(byte)payloadLength});
                _offset+=1;
            } else {
                byte[] payloadLengthArray = new byte[4];
                in.read(payloadLengthArray, 0, payloadLengthArray.length);
                //System.out.println("payloadArray to parse: " + Utils.toFixedHexString(payloadLengthArray));
                payloadLength = (int)(payloadLengthArray[0] << 24) + (int)(payloadLengthArray[1] << 16) + (int)(payloadLengthArray[2] << 8) + (int)(payloadLengthArray[3] & 0xff);
//                int test = (payloadLengthArray[2] << 8);
//                System.out.println("test: "+test);

                _outputPayloadLength(_offset, payloadLength, payloadLengthArray);
                _offset+=4;
            }
            //System.out.println("payloadLength="+payloadLength);

            payloadLength &= 0x1FFFFFFF; // TODO: Ful-hack



            int idLength;
            if((hdr & IL) != 0) {
                idLength = in.read();
                System.out.println(_offset++ + "\t0x"+Integer.toHexString(idLength)+"\tLength of ID ("+idLength+" byte)");
            } else {
                idLength = 0;
            }

            byte[] type = new byte[typeLength];
            in.read(type, 0, type.length);
            NDEFRecordType recordType;
            switch(hdr & TNF_MASK) {
                case 0x00:
                    recordType = new NDEFRecordType(NDEFRecordType.EMPTY, "");
                    break;
                case 0x01:
                    recordType = new NDEFRecordType(NDEFRecordType.NFC_FORUM_RTD, "urn:nfc:wkt:"+new String(type));
                    break;
                case 0x02:
                    recordType = new NDEFRecordType(NDEFRecordType.MIME, new String(type));
                    break;
                case 0x03:
                    recordType = new NDEFRecordType(NDEFRecordType.URI, new String(type));
                    break;
                case 0x04:
                    recordType = new NDEFRecordType(NDEFRecordType.EXTERNAL_RTD, "urn:nfc:ext:"+new String(type));
                    break;
                case 0x06:
                    throw new RuntimeException("Record type 'unchanged' not implemented!");
                case 0x05:
                default:
                    recordType = new NDEFRecordType(NDEFRecordType.UNKNOWN, new String(type));
                    break;
            }
            _outputType(_offset, type, recordType.getName());
            _offset+=type.length;

            byte[] id = new byte[idLength];
            in.read(id, 0, id.length);
            if(id.length > 0) {
                _outputID(_offset, id);
                _offset+=id.length;
            }

            byte[] payload = new byte[payloadLength];
            in.read(payload, 0, payload.length);            

            if("Sp".equals(recordType.getName())) {
                _offset = parseMessage(_offset, sb, payload, 0);
            } else if(URIRecord.RECORD_TYPE.equals(recordType)) {
                //System.out.println("recordType is URI: " + recordType.getName());
                //_offset = parseURIRecord(_offset, sb, payload);
                sb.append(_offset);
                sb.append("\t");
                sb.append(" \tTODO: URI-payload\n");
                _offset += payload.length;
            } else if("Sig".equals(recordType.getName())) {
                sb.append(_offset + "\t \t TODO: Sig payload");
            } else {
                //TODO: Parse other types
                sb.append(_offset + "\t \t TODO: Other type");
            }

        } while((hdr & ME) == 0);

        return _offset;
    }

    private int parseURIRecord(int _offset, StringBuffer sb, byte[] payload) {
        sb.append(_offset);
        sb.append("\t");
        sb.append("**URI-payload**\n");
        return _offset+payload.length;
    }

    /*private int parseSigRecord(int _offset, StringBuffer2 sb, byte[] payload) {
        try {
            ByteArrayInputStream in = new ByteArrayInputStream(payload);
            byte temp;

            // Version Field
            version = read(in);

            if(version != 0x01) {
                throw new InvalidSignatureRecord(InvalidSignatureRecord.VERSION, "Unexpected version: " + version);
            }

            // Signature Field
            int signatureType;
            byte[] signature = null;
            URIRecord signatureUri = null;
            temp = (byte) read(in);
            boolean signatureUriPresent = ((temp & 0x80) != 0);
            signatureType = temp & 0x7f;

            if(isSignatureRTD && signatureType >= 0x05) {
                throw new InvalidSignatureRecord(InvalidSignatureRecord.SIGNATURE_TYPE, "Unexpected signature type: " + signatureType);
            }
            marker = (signatureUriPresent == false && signatureType == 0);

            if(marker) {
                this.signatureField = SignatureField.createMark();
                this.certificateField = CertificateField.createEmpty();
            } else {
//                int sigLen = read(in)&0xff << 8 | read(in)&0xff;
                int sigLen;
                {
                    int high = read(in)&0xff;
                    int low = read(in)&0xff;
                    high <<= 8;
                    sigLen = (high | low)&0xffff;
                }

//                System.out.println("sigLen: " + sigLen);

                byte[] sigURIField = new byte[sigLen];
                read(in, sigURIField);

                if(!signatureUriPresent) {
                    signature = sigURIField;
                } else {
                    NDEFRecord msg = new NDEFRecord(sigURIField, 0);
                    signatureUri = new URIRecord(msg.getPayload(), 0);
                }

                // Certificate Chain Field
                temp = read(in);
                boolean uriPresent = (temp & 0x80) != 0;

                int certificateFormat = (temp & 0x70) >> 4;
                if(isSignatureRTD && certificateFormat >= 0x02) {
                    throw new InvalidSignatureRecord(InvalidSignatureRecord.CERTIFICATE_FORMAT, "Unexpected certificate format: " + certificateFormat);
                }

                int numCerts = temp & 0x0f;

                CertificateBytes[] certificates = new CertificateBytes[numCerts];
                for(int i = 0; i < numCerts; i++) {
                    int high = read(in)&0xff;
                    int low = read(in)&0xff;
                    high <<= 8;
                    int len = (high | low)&0xffff;

                    byte[] bytes = new byte[len];
                    read(in, bytes);
                    certificates[i] = new CertificateBytes(bytes);
                }

                URIRecord certificateUri = null;
                if(uriPresent) {
                    NDEFRecord msg = new NDEFRecord(payload, payload.length-in.available());
                    certificateUri = new URIRecord(msg.getPayload(), 0);
                }

                if(signatureUri != null) {
                    this.signatureField = new SignatureField(signatureType, signatureUri);
                } else {
                    this.signatureField = new SignatureField(signatureType, signature);
                }
                this.certificateField = new CertificateField(certificateFormat, certificates, certificateUri);
            }

        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
        return _offset;
    }*/

    private static void _outputHeader(int offset, int hdr) {
        StringBuffer sb = new StringBuffer();
        sb.append(offset);
        sb.append("\t");

        sb.append("0x");
        sb.append(Integer.toHexString(hdr&0xff).toUpperCase());
        sb.append("\t");

        sb.append("MB=" + ((hdr & MB) == 0 ? 0 : 1) + ",");
        sb.append("ME=" + ((hdr & MB) == 0 ? 0 : 1) + ",");
        sb.append("SR=" + ((hdr & SR) == 0 ? 0 : 1) + ",");
        sb.append("TNF=" + Integer.toHexString(hdr & TNF_MASK) + ",");

        System.out.println(sb.toString());
    }

    private static void _outputPayloadLength(int offset, int length, byte[] payloadLengthArray) {
        StringBuffer sb = new StringBuffer();
        sb.append(offset);
        sb.append("\t");
        for(int i = 0; i < payloadLengthArray.length; i++) {
            sb.append("0x");
            sb.append(Integer.toHexString(payloadLengthArray[i]&0xff));
            sb.append(" ");
        }
        sb.append("\t");
        sb.append("Length of the payload (");
        sb.append(length);
        sb.append(" bytes)");

        System.out.println(sb.toString());
    }

    private static void _outputID(int offset, byte[] value) {
        StringBuffer sb = new StringBuffer();
        sb.append(offset);
        sb.append("\t");
        for(int i = 0; i < value.length; i++) {
            sb.append("0x");
            sb.append(Integer.toHexString(value[i]&0xff));
            sb.append(" ");
        }
        sb.append("\t");
        sb.append("ID");

        System.out.println(sb.toString());
    }

    private static void _outputType(int offset, byte[] value, String sType) {
        StringBuffer sb = new StringBuffer();
        sb.append(offset);
        sb.append("\t");
        for(int i = 0; i < value.length; i++) {
            sb.append("0x");
            sb.append(Integer.toHexString(value[i]&0xff));
            sb.append(" ");
        }
        sb.append("\t");
        sb.append("The record name (\"");
        sb.append(sType);
        sb.append("\")");

        System.out.println(sb.toString());
    }
}
